#include "../Headers/cluster.h"

cluster*
cl_new() {
    cluster* new_cluster;
    new_cluster = (cluster*)malloc(sizeof(cluster));
    cl_init(new_cluster);
    return new_cluster;
}

cl_list*
cl_list_new() {
    cl_list* new_list;
    new_list = (cl_list*)malloc(sizeof(cl_list));

    new_list->cl = NULL;
    new_list->next = NULL;

    return new_list;
}

/*!
 * Initializes a cluster so that all of its attributes point at NULL.
 *
 * This shouldn't need to be called in any location other than cl_new.
 */
void
cl_init(cluster* cl) {
    //cl->name = 0  // This will be initialized (to some other value) by the
                    // central registry.
    cl->create_time = TIME_ERROR;  // This is the time relative to the
                                   // containing scope. If it isn't re-assigned,
                                   // it's an error.
    cl->name = ":unnamed";
    cl->identity = NULL;
    cl->description = NULL;
    cl->attributes = NULL;
    cl->jobs = NULL;
    cl->job_memos = NULL;

    cl->scope = NULL;
    cl->dependencies = NULL;

    cl->data = NULL;

    cl->parents = NULL;
    cl->grammar = NULL;
    cl->job_handler = NULL;
    cl->invoke_func = NULL;
    cl->destroy_func = NULL;
    cl->mutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));

    pthread_mutex_init(cl->mutex, NULL);
    cl->ref_count = 0;
}

void
cl_set_name(cluster* cl, symbol name) {
    cl_mutex_lock(cl);
    // +1 for the \0...
    cl->name = (char*)malloc(sizeof(char) * (strlen(name) + 1));
    // copy over all, including the \0...
    strcpy(cl->name, name);
    cl_mutex_unlock(cl);
}

void
cl_set_invoke_func(cluster* cl, cluster* (*func)(cluster*)) {
    cl->invoke_func = func;
}

void
cl_set_destroy_func(cluster* cl, void (*func)(cluster*)) {
    cl->destroy_func = func;
}

void
cl_append(cl_list** list_loc, cluster* to_append) {
    cl_list* cursor;

    if (*list_loc == NULL) {
        cursor = cl_list_new();
        cursor->cl = to_append;
        *list_loc = cursor;
    } else {
        cursor = *list_loc;
        while (cursor->next != NULL) {
            cursor = cursor->next;
        }
        cursor->next = cl_list_new();
        cursor = cursor->next;
        cursor->cl = to_append;
        cursor->next = NULL;
    }

    // This is the appropriate place to register everything because there is
    // expected to be some time between when a cluster is created and when it
    // gets a reference count. This is the first place we can register a cluster
    // and be certain the garbage collector won't immediately free it.
    cl_register(to_append);
}

cluster*
cl_remove(symbol name, cl_list** list_loc) {
    cl_list *cursor, *marker;
    cluster* retval;

    cursor = *list_loc;
    marker = cursor;
    // If cursor is NULL, that's an error and I want to know about it.
    while (cursor->cl->name != name) {
        marker = cursor;
        cursor = cursor->next;
    }

    // Happens when we want to remove the first item. In this case, we need
    // to modify the list so that it points at the new head.
    if (cursor == marker) {
        *list_loc = cursor->next;
    } else {
        marker->next = cursor->next;
    }
    retval = cursor->cl;
    free(cursor);
    if (--retval->ref_count == 0) {
        cl_unregister(retval);
    }

    return retval;
}

void
cl_empty_list(cl_list** list_loc) {
    cl_list *cursor, *marker;
    cursor = *list_loc;
    while (cursor != NULL) {
        marker = cursor->next;
        if (--cursor->cl->ref_count == 0) {
            cl_unregister(cursor->cl);
        }
        cursor = marker;
    }
    *list_loc = NULL;
}

cluster*
cl_read(symbol name, cl_list* list) {
    // Not naming a new cl_list* because list is a copy of the list pointer.
    while (list && list->cl->name != name) {
        list = list->next;
    }

    if (list) {
        return list->cl;
    } else {
        DEBUG("TODO: return Attribute error\n");
        return (cluster*)NULL;
    }
}

//! @TODO: (flesh this out after finishing ECE homework.)
// Meld cl_invoke and cl_process_jobs. The cl_invoke function should do the following things:
// 1) Check to see if all of its dependencies exist in the job_memos list. If any are absent, then place the
// dependencies in the jobs list.
// 2) Process all of the jobs in the jobs list. Note, if a job is dependent on something that is not in the
// job_memos
//
// If a job is dependent on another job, how do we handle that? The job should block until its dependency
// is resolved...
// How do I work in the time indexing system? A function with (a = b; b = a) would produce a deadlock with what
// I've described, assuming the time system is on hiatus.
// I should just work out some more examples concerning this...
cluster*
cl_invoke(cluster* cl) {
    //! @TODO Evaluate whether this needs to pay extra attention to thread
    // safety.

    if (cl->invoke_func) {
        return cl->invoke_func(cl);
    } else {
        //! @TODO If cl->invoke_func is NULL, return the Error_cl reference and
        // update the time indexed attribute to give error information.
        DEBUG("WARNING! A cluster with no defined invoke func was invoked.\n");
        return NULL;
    }
}

void
cl_invoke_destroy(cluster* cl) {
    if (cl->destroy_func) {
        cl->destroy_func(cl);
    } else {
        DEBUGF("WARNING! Cluster %s with no defined destroy func was invoked.\n", cl->name);
    }

    cl_empty_ident_list(&cl->identity);
    cl_empty_list(&cl->description);
    cl_empty_list(&cl->attributes);
    //cl_empty_list(&cl->jobs);
    //cl_empty_list(&cl->job_memos);
    cl_empty_list(&cl->scope);
    //cl_empty_list(&cl->dependencies);
    cl_empty_list(&cl->parents);
    cl_empty_list(&cl->grammar);
}

cluster*
cl_set_create_time(cluster* cl, cl_time_t time) {
    cl->create_time = time;
    return cl;
}

void
cl_mutex_lock(cluster* cl) {
    pthread_mutex_lock(cl->mutex);
}

void
cl_mutex_unlock(cluster* cl) {
    pthread_mutex_unlock(cl->mutex);
}

void
cl_add_job(job_list** job_list_loc, job_list* job) {
    job->next = *job_list_loc;
    *job_list_loc = job;
}


void
cl_remove_job(job_list** job_list_loc, job_list* job) {
    job_list *cursor, *marker;
    cursor = *job_list_loc;

    if (cursor == NULL) {
        return;
    } else if (cursor == job) {
        *job_list_loc = cursor->next;
    } else {
        while (cursor != NULL && cursor != job) {
            marker = cursor;
            cursor = cursor->next;;
        }

        if (cursor == NULL) {
            return;
        } else {
            marker->next = cursor->next;
            free(cursor);
        }
    }
}


void
info_dump_list(cl_list* list) {
    while (list != NULL) {
        DEBUGF("----element name: %s ref_count: %d\n", list->cl->name, list->cl->ref_count);
        list = list->next;
    }
}

void
info_dump(cluster* cl) {
    DEBUG("******** INFO DUMP ********\n");
    DEBUGF("**cluster name: %s\n", cl->name);
    DEBUGF("**create time: %d\n", cl->create_time);
    DEBUGF("**ref_count: %d\n", cl->ref_count);
    //DEBUG("**identity:\n");
    //info_dump_list(cl->identity);
    DEBUG("**description:\n");
    info_dump_list(cl->description);
    DEBUG("**attributes:\n");
    info_dump_list(cl->attributes);
    //DEBUG("**jobs\n");
    //info_dump_list(cl->jobs);
    DEBUG("**scope\n");
    info_dump_list(cl->scope);
    //DEBUG("**dependencies\n");
    //info_dump_list(cl->dependencies);
    DEBUG("**parents\n");
    info_dump_list(cl->parents);
    DEBUG("**grammar\n");
    info_dump_list(cl->grammar);
    DEBUG("****** end info dump ******\n");
}
